[BITS 16]
[ORG 0x7c00]

;The FAT documentation was based on 
;http://www.wotsit.org/download.asp?f=fat32&sc=280505868

;the logical sector were the root entries start
%define FAT_ROOT_START		19		; = ReservedSectors + NumFATS*FatSize16 = 1 + 2*9 = 19
%define FAT_DATA_START		33-2	; = FAT_ROOT_START + RootDirSize (224 * 32 / 18) = 19 + 14 = 33
											;the minus 2 is to compensate the fact that entry 3 in the FAT is at
											;sector 1 of data sector
%define FAT_ONE_START		1 		; = The sector were the FAT is (= ReservedSectors)
;buffer related stuff
%define SECTORS_PER_READ	2		;how many sectors we read in each 
%define BUFFER_SIZE			512*SECTORS_PER_READ	;the size of the buffer after the bootloader

;Where the kernel will be loaded: boot addres + booloader + buffer + stack
%define KERN_DEST				0x7C00+512+BUFFER_SIZE+4096



jmp short boot_start ;we short jump to the bootstrap section
nop						;nop is needed becase we short-jumped
							;if we had near jumped it wasn't necessary

;FAT formated Header
;The first part is common to FAT12/16/32
OEMName 					db "mkdosfs " 		;OEM Name
BytesPerSec				dw	512				;bytes per sector
SectorsPerCluster		db	1					;Sectors per cluster
ReserverdSectors		dw	1					;Reserved sectors (must be 1 for FAT12/16)
NumFATS					db	2					;2 FATs
RootEntries				dw	224				;Number of entries in the root directory
TotSectors16			dw	2880				;Number of sectors on the disk
MediaType				db	0xF0				;Media type (Floppy)
FatSize16				dw	9					;Size, in clusters, of each FAT
SectorsPerTrack		dw	18					;Sectors per track
NumberOfHeads			dw	2					;Number of sides/heads of the disk
HiddenSectors			dd	0					;Number of hidden sectors
TotSectors32			dd	0					;Number of sectors (used for FAT32)
;From now on its FAT12/16 specific
DriveNumber				db	0					;Drive number = 0x00 for floppy
Reserved					db	0					;Reserved
BootSignature			db	0x29				;Signature, to show that the following fields are present
VolumeID					dd	0x00000000		;VolumeID: watever you want
VolumeLabel				db	"K-OS System"	;Volume Label, 11B wide.
FileSystemType			db "FAT12   "		;The filesystem type (not mandatory, but should be right)

boot_start:
	cli

	;Make sure we are working with segments with zeroes
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov fs, ax

	;Save the floppy drive we are booted of
	mov [floppy], dl

	;After the end of the bootloader we have a BUFFER_SIZE buffer
	;We will setup a 4KB stack that will start after the buffer
	;0x8200 = 0x7C00 + BUFFER_SIZE (booloader + buffer)
	;but since this a segmente register in real mode, we need to divide it by 16
	mov ax, (0x7C00 + 512 + BUFFER_SIZE)/16
	mov ss, ax
	mov sp, 4096 ;a 4k stack

	;enable the A20 line, so we can access more than 1Mb of memory
	call A20_enable

	;get memory size and map

	;find the sector where the kernel image is
	call find_os

	;now that we know were it is, load it at KERN_DEST
	call load_os

	;Load a gdt, this is a must to go to protected mode
	lgdt [gdtr]

	;move to protected mode and start the kernel
	;code from: http://wiki.osdev.org/Protected_mode
	mov eax, cr0
	or al, 1
	mov cr0, eax
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	jmp 0x8:KERN_DEST

;;;;;;;;;;;;
;Functions
;;;;;;;;;;;;

A20_enable:
;Code to enable the A20 line, we don't do any enable/disabled test
;since it has to be enabled anyway, and this code SHOULD enable it
;from Linux 0.01 boot/boot.s
;IN : Nothing
;Out: The A20 line enable 

	call	empty_8042
	mov	al, 0xD1
	out	0x64, al
	call	empty_8042
	mov	al, 0xDF
	out  	0x60, al
	call	empty_8042

empty_8042:
;	.word	0x00eb,0x00eb
	in		al, 0x64		;8042 status port
	test	al, 2			;is input buffer full?
	jnz	empty_8042	;yes - loop
	ret

printstr:
;A simple function to print a string on the screen using the BIOS
;The strings are C like, with a \0 at the end.
;IN : The address of the first element of the string in SI
;OUT: Nothing
	pusha				;we will mess with registers, so save them
	mov ah, 0xE		;funcion 0xE: print in teletype mode
	.loop:
		lodsb			;get the string from SI and put it at AL
		cmp al, 0	;its a zero?
		je .done		;if so we are done
		int 10h		;if not print it
		jmp .loop	;and go back
	.done:
	popa				;now restore the registers
	ret

find_os:
;This looks up the disk where we booted from and looks for a file
;named KOSKERN.BIN which is the kernel of our system.
;IN : nothing really ^_^
;OUT: the number of the first sector of the kernel image on AX
	mov ax, FAT_ROOT_START		;ax will count how many sectors we have checked
	.read_next_sector:			;we load one sector at a time
		;we are on a floppy with 224 root entries, that is: 14 sectors
		cmp ax, FAT_ROOT_START + 14 
		;So, if we have already read all 14 sectors, the file is not there
		jg	.file_not_found
   	call read_floppy_sectors ;read the AXth sector and store it on the buffer
		xor bx, bx					;bx will count how many entries in the current sector we've checked

	.entries_loop:
	   ;compare strings at ES:DI and DS:SI, 
		;during 11 characters until one of them aren't equal or cx = 0
		mov si, buffer			;the sector we've read form teh disk
		add si, bx				;check the BXth entry, so to say...
	   mov di, kern_name		;compare it with our kernel name
	   mov cx, 11				;the length of the file name is 11 chars
		repe cmpsb				;go on and compare them
	   je .entry_found 		;It ended with an equal, so the strings are equal
		add bx, 32				;if they aren't equal mov to the next entry
		cmp bx, BUFFER_SIZE	;have we checked all entries on the buffer
		jl .entries_loop		;we still have entries to read in this sector
		add ax, SECTORS_PER_READ	;no more entries in these sectors, go on to the next one
		jmp short .read_next_sector

	.entry_found:
		;we have found the entry, the sector address is at bytes 26 and 27
		mov ax, [buffer+bx+26]	;save it on AX
		mov si, msg					;print a message that we will be booting
		call printstr
		ret							;and go back

	.file_not_found:	;the file was not found
		mov si, file_not_found
		call printstr
		hlt

load_os:
;Function to load the OS 
;It will read KOSKERN.BIN from the floppy and put it on 0xF000
;IN : The first sector of the file on AX
;Out: The OS loaded at 0xF000
	xor dx, dx		;dx keeps track of the destination of the kernel
	;read the current sector from the disk and move it to KERN_DEST
	.read_next_sector:
		add ax, FAT_DATA_START
		call read_floppy_sectors
		mov cx, [BytesPerSec]
		mov si, buffer
		mov di, KERN_DEST
		add di, dx
		rep movsb
		add dx, [BytesPerSec]

	;now get from the FAT which is the next sector
	;To find out in which sector of the FAT is the entry we are looking for we do
	;FAT_SEC = BYTES_INTO_FAT / BYTES_PER_SEC
	;where, BYTES_INTO_FAT = (SECTOR * 12) / 8
	;And in which byte of this sector is the entry
	;BYTES_INTO_SEC = BYTES_INTO_FAT % BYTES_PER_SEC
	;Also, note that if (SECTOR * 12) % 8, is equals four, than we need to shift
	;the entry 4 bits, so that its on the last significant bits
	;If its four, than we need to and it with 0FFFh, to erase the most significant bits
	;that aren't part of the entry
	;SO... let's begin
	push dx						;save dx, we will need it...
	sub ax, FAT_DATA_START	;get back the logical sector we are on
	;AX = BYTES_INTO_FAT = (SECTOR * 12) / 8
	mov cx, 12				
	mul cx						;each entry has 12 bits
	mov cx, 8
	xor dx, dx
	div cx						;each sector has 512 bytes
	push dx						;save the remainder os this division
	;FAT_SEC = BYTES_INTO_FAT / BYTES_PER_SEC
	mov cx, [BytesPerSec]
	xor dx, dx
	div cx
	;AX = FAT_SEC
	;DX = BYTES_INTO_SEC
	add ax, FAT_ONE_START	;add the FAT displacement
	call read_floppy_sectors;and read
	mov bx, dx
	add bx, buffer				;read DX bytes into the buffer to get the entry
	mov ax, [bx]				;put it in AX, where is belongs
	pop dx
	cmp dx, 0					;no check if we need to move some bits
	je .doand 					;if its 0 do an AND
		shr ax, 4				;otherwise shift it 4 bits down and let it split 
	.doand:						;so we save one command ;)
		and ax, 0x0FFF			;bits already in pos, zero the first bits
	.done:
	pop dx						;this is the DX that counts the bytes we have copied...
	cmp ax, 0x0FF8				;check if its an EOF
	jl .read_next_sector		;if not read the next sector
	ret							;otherwise we are done

read_floppy_sectors:
;Read some sectors from the floppy/disk we were booted from
;IN : the logical sector to read on AX
;OUT: the contents of the sector on the buffer
;Based on: http://en.wikipedia.org/wiki/Int_13

	;First thing is to convert the LBA to CHS
	;http://en.wikipedia.org/wiki/CHS_conversion#From_LBA_to_CHS
	pusha								;We will be using all registers to calculate the CHS
	mov cx, ax						;CX will hold the LBA
	
	;CYL = LBA / (HPC * SPT)
	;TEMP = LBA % (HPC * SPT)
	mov ax, [NumberOfHeads] 	;AX = HPC
	mul word [SectorsPerTrack] ;AX = HPC * SPT
	xchg ax, cx						;AX = LBA, CX = HPC * SPT
	xor dx, dx
	div cx							;AX = CYL, DX = temp
	mov cx, ax						;CX = CYL
	mov ax, dx						;AX = temp
	;HEAD = TEMP / SPT
	;SECT = TEMP % SPT + 1
	xor dx, dx						;this is a "64bit" division, so DX needs to be zero
	div word [SectorsPerTrack]	;AX = HEAD; DX = SECT - 1
	inc dx							;DX = SECT
	rol cx, 8						;make room for the sectors in CX
	shl cl, 6
	or cx, dx						;CX = track + sector
	mov dh, al						;DH is the Head
	
	;Now setup the other values to the function call
	mov dl, [floppy] 				;which driver we will be reading from
	;AH=Extended Read Sectors From Drive function (02h)
	mov ah, 0x02
	;AL=number of sectors to read
	mov al, SECTORS_PER_READ
	mov bx, buffer					;ES:BX destination of bytes read (ES=0)
	stc								;set the carry flag, so we know if there was an error
	int 13h							;call it
	jc .error						;If there was an error CF=1
	popa	 							;Get the registers back
	ret
	.error:							;an error happened
		mov si, floppy_err		;print a msg
		call printstr
		hlt							;and halt

;;;;;;;;;;;;;;;;
;DATA (VARIABLES, CONSTANTS AND STUFF...)
;All strings should end with a 0 for printstr to work
;"D" is Carriege Return and "A" is Line Feed
;;;;;;;;;;;;;;;;
msg 				db "K-OS booting...",0xD,0xA,0
floppy_err		db "Floppy error.",0xD,0xA,0
file_not_found	db	"KOSKERN.BIN not found.",0xD,0xA,0

kern_name		db "KOSKERN BIN" ;the name of the kernel on the floppy

floppy 			db 0	;the floppy ID we were booted from

;This is the GDT, needed to go to protected mode
;for know just set two segments for ring 0, later, in the OS, we do it again
;a good grasp of what is this can be found in 
;http://wiki.osdev.org/GDT
gdtr:
	dw	3 * 8 - 1	;we have 3 4bytes entries, minus 1 since it counts from zero
	dq gdt_table	;the table is in gdt_table
	
;the table itself
;each entry is 4 bytes long
gdt_table:
	times 8 db 0 ;the first entry has all values as zero
	;second entry: kernel code
	dw	0xFFFF	;limit 7-0 and limit 15-8 (go all the way to 4G)
	dw 0x0000	;base 7-0 and base 15-8 (start at zero)
	db 0x00		;base 23-16
	db	10011010b;access bytes
	db	11001111b;flags and limit 1-16
	db 0x00		;base 31-24
	;third entry: kernel data
	dw	0xFFFF	;limit 7-0 and limit 15-8 (go all the way to 4G)
	dw 0x0000	;base 7-0 and base 15-8 (start at zero)
	db 0x00		;base 23-16
	db	10010010b;access bytes
	db	11001111b;flags and limit 1-16
	db 0x00		;base 31-24
	
times 512-($-$$)-2 db 0
dw 0xAA55

;this is the end of the bootloader
;after it we have a buffer, that spams for BUFFER_SIZE
buffer: 
